﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using FxSuperCore.Log;
using MemHack.Util;

namespace MemHack.Mem
{
	public class MemoryHack
	{
		public Process Process { get; private set; }

		// 开启debug模式
		public bool DebugMode { get; set; }

		// 日志打印
		public ILogPrint Logger { get; set; } = new PluginLoggerPrinter("MemHack", new ConsoleLogPrinter());

		// 搜索器
		public ISearchByte Searcher { get; set; } = new SundaySearcher();

		// 搜索内存页模式
		public uint SearchMemProtectMode { get; set; } = (uint)( PAGE_PROTECT.PAGE_READWRITE | PAGE_PROTECT.PAGE_EXECUTE_READ | 
		                                                         PAGE_PROTECT.PAGE_EXECUTE_READWRITE | PAGE_PROTECT.PAGE_READONLY);

		// 单次搜索每个搜索线程最大内存占用
		public uint MaxOnceSearchMemSizePerThread { get; set; } = 50 * 1024 * 1024;

		// 最大的搜索线程数
		public int MaxSearchThreadNum { get; set; } = 4;

		// 存放虚拟内存信息
		internal LinkedList<PMEMORY_BASIC_INFORMATION32> MemInfos = new LinkedList<PMEMORY_BASIC_INFORMATION32>();

		// 当前进程占用的总可读虚拟内存大小
		internal ulong VirtualCanReadMemSize { get; private set; } = 0;

		/// <summary>
		/// 开启进程
		/// </summary>
		/// <param name="processName">进程是否开启成功</param>
		/// <returns>进程是否开启成功</returns>
		public void Open(string processName)
		{
			if (processName.ToLower().EndsWith(".exe"))
			{
				processName = processName.Substring(0, processName.LastIndexOf("."));
			}
			Process[] processes = Process.GetProcessesByName(processName);
			if (processes.Length > 0)
			{
				OpenProcess(processes[0]);
				return;
			}
			throw new Exception($"未获取到指定的进程信息:processName: {processName}");
		}

		/// <summary>
		/// 开启进程
		/// </summary>
		/// <param name="pid">进程pid</param>
		/// <returns>是否开启成功</returns>
		public void Open(int pid)
		{
			Process process = Process.GetProcessById(pid);
			if (process == null)
			{
				throw new Exception($"未获取到指定的进程信息: pid: {pid}");
			}
			OpenProcess(process);
		}

		private void OpenProcess(Process process)
		{
			this.Process = process;
		}

		public void Log(object msg, bool debugLog = true)
		{
			if (debugLog && !DebugMode)
			{
				return;
			}

			if (Logger == null)
			{
				return;
			}

			if (debugLog)
			{
				Logger.Debug(msg);
			}
			else
			{
				Logger.Info(msg);
			}
		}

		public virtual bool Is64Bit()
		{
			if (!Environment.Is64BitOperatingSystem)
			{
				return false;
			}
			bool ret;
			NativeCall.IsWow64Process(this.Process.Handle, out ret);

			if (RuntimeInformation.OSArchitecture == System.Runtime.InteropServices.Architecture.Arm || 
			    RuntimeInformation.OSArchitecture == System.Runtime.InteropServices.Architecture.Arm64)
			{
				return ret;
			}

			return !ret;
		}

		public virtual ProcessModule[] FindModule(string name, bool ignoreCase = false)
		{
			var modules = new LinkedList<ProcessModule>();
			foreach (ProcessModule module in Process.Modules)
			{
				bool isFind = ignoreCase ? module.ModuleName.ToLower() == name.ToLower() : module.ModuleName == name;
				if (isFind)
				{
					modules.AddLast(module);
				}
			}

			return modules.ToArray();
		}

		public void WriteBytes(long address, byte[] bytes)
			=> NativeCall.WriteProcessMemory(Process.Handle, new IntPtr(address), bytes, bytes.Length, IntPtr.Zero);

		public void WriteData<T>(long address, T data) where T : unmanaged
			=> WriteBytes(address, MemUtil.ConvertBeseTypeToBytes(data));

		public void WriteString(long address, string text, string encoding = "UTF-8")
			=> WriteBytes(address, Encoding.GetEncoding(encoding).GetBytes(text));

		public byte[] ReadBytes(long address, int len)
		{
			byte[] tempBytes = new byte[len];
			IntPtr byteAddr = Marshal.UnsafeAddrOfPinnedArrayElement(tempBytes, 0);
			NativeCall.ReadProcessMemory(Process.Handle, new IntPtr(address), byteAddr, tempBytes.Length, IntPtr.Zero);
			return tempBytes;
		}

		public T ReadData<T>(long address) where T : unmanaged
			=> MemUtil.ConvertBytesToBaseType<T>(ReadBytes(address, MemUtil.SizeOfBaseType<T>()));

		public string ReadString(long address, int maxLen = 200, string encoding = "UTF-8")
			=> Encoding.GetEncoding(encoding).GetString(ReadBytes(address, maxLen));

		public virtual void RefreshVirtualMemInfo(long startAddress = 0)
		{
			MemInfos.Clear();
			VirtualCanReadMemSize = 0;
			IntPtr addr = new IntPtr(startAddress);
			PMEMORY_BASIC_INFORMATION32 memInfo;
			uint size = NativeCall.VirtualQueryEx(Process.Handle, addr, out memInfo,
				(uint) Marshal.SizeOf<PMEMORY_BASIC_INFORMATION32>());
			uint max = 0;

			while (size > 0)
			{
				MemInfos.AddLast(memInfo);
				if (MemUtil.MemCanRead(memInfo.Protect))
				{
					VirtualCanReadMemSize += memInfo.RegionSize;
					max = Math.Max(max, memInfo.RegionSize);
				}
				addr = IntPtr.Add(memInfo.BaseAddress, (int)memInfo.RegionSize);
				size = NativeCall.VirtualQueryEx(Process.Handle, addr, out memInfo,
					(uint)Marshal.SizeOf<PMEMORY_BASIC_INFORMATION32>());
			}
			Log($"总可读虚拟内存大小:{VirtualCanReadMemSize / 1024 / 1024}mb");
			Log($"最大可读内存size为:{max / 1024 / 1024}mb");
		}

		public virtual long[] SearchBytes(byte[] bytes, ulong startAddr = 0, ulong endAddr = ulong.MaxValue)
		{
			MultiThreadSearch threadSearch = new MultiThreadSearch(this);
			return threadSearch.Search(bytes, false, startAddr, endAddr);
		}

		public virtual long[] SearchNumber<T>(T data, bool byteAlign = true, ulong startAddr = 0, ulong endAddr = ulong.MaxValue) where T : unmanaged
		{
			byte[] searchBytes = MemUtil.ConvertBeseTypeToBytes(data);

			MultiThreadSearch threadSearch = new MultiThreadSearch(this);
			return threadSearch.Search(searchBytes, byteAlign, startAddr, endAddr);
		}

		public virtual long[] SearchText(string text, string encoding = "UTF-8", ulong startAddr = 0, ulong endAddr = ulong.MaxValue)
		{
			byte[] bytes = Encoding.GetEncoding(encoding).GetBytes(text);
			return SearchBytes(bytes, startAddr, endAddr);
		}

		/// <summary>
		/// 执行自动汇编脚本
		/// </summary>
		/// <param name="script">脚本详情</param>
		public virtual IExecuteAssemblyScript AutoAssembly(string script)
		{
			return new DefaultAutoAssembly(this, script);
		}

		public long AllocVirtualMem(uint size, long startAddr = 0)
		{
			IntPtr addr = NativeCall.VirtualAllocEx(Process.Handle, new IntPtr(startAddr), size, (uint) MEM_TYPE.MEM_COMMIT,
				(uint) PAGE_PROTECT.PAGE_EXECUTE_READWRITE);
			Log($"分配地址:{addr.ToInt64():X}");
			return addr.ToInt64();
		}

		public bool FreeVirtualMem(long addr)
		{
			Log($"释放地址:{addr:X}");
			return NativeCall.VirtualFreeEx(Process.Handle, new IntPtr(addr), 0, (uint) MEM_TYPE.MEM_RELEASE);
		}

		public virtual bool InjectDll(string dllPath)
		{
			if (File.Exists(dllPath) == false)
			{
				Log($"dll注入失败，未找到dll:{dllPath}!");
				return false;
			}

			long paramAddr = AllocVirtualMem(0x1000);
			WriteString(paramAddr, dllPath);
			var module = FindModule("Kernel32.dll", true).FirstOrDefault();
			if (module == null)
			{
				Log($"未获取到模块: Kernel32.dll");
				return false;
			}
			
			IntPtr funcAddr = NativeCall.GetProcAddress(module.BaseAddress, "LoadLibraryA");
			int res = NativeCall.CreateRemoteThread(Process.Handle, 0, 0, funcAddr, new IntPtr(paramAddr), 0, 0);
			if (res != 0)
			{
				return true;
			}

			return false;
		}

		public virtual void Close()
		{
			Process = null;
			MemInfos.Clear();
		}
	}
}
